package com.google.code.mochaccino.framework.crypto.x509;

import com.google.code.mochaccino.framework.crypto.EncryptionException;
import com.google.code.mochaccino.framework.crypto.keystore.KeyStoreManager;
import java.net.Socket;
import java.security.KeyStore;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.X509KeyManager;

/**
 * A X509 Key Manager Implementation that aggregates multiple X509 Key Manager into
 * a single one. Albeit odd as a concept, it allows to on-the-fly changes the accepted
 * key managers at runtime.
 */
public final class X509KeyManagerAggregator implements X509KeyManager {

	/** The Key Manager */
	private List<X509KeyManager> managers = Collections.synchronizedList( new ArrayList<X509KeyManager>() );

	/** Constructor */
	public X509KeyManagerAggregator() {
	}

	/** Add Key Manager */
	public void addKeyManager( String algo, KeyStore keyStore, char[] passphrase ) throws EncryptionException {
		addKeyManager( algo, null, keyStore, passphrase );
	}

	/** Add Key Manager */
	public void addKeyManager( String algo, KeyStoreManager keyStore ) throws EncryptionException {
		addKeyManager( algo, null, keyStore.getKeyStore(), keyStore.getPassword().toCharArray() );
	}

	/** Add Key Manager */
	public void addKeyManager( String algo, Provider provider, KeyStore keyStore, char[] passphrase ) throws EncryptionException {
		try {
			KeyManagerFactory keyManagerFactory = provider != null ? KeyManagerFactory.getInstance( algo, provider.getName() ) : KeyManagerFactory.getInstance( algo );
			keyManagerFactory.init( keyStore, passphrase );
			for ( KeyManager manager : keyManagerFactory.getKeyManagers() ) {
				if ( manager instanceof X509KeyManager ) {
					addKeyManager( (X509KeyManager) manager );
				}
			}
		} catch ( Exception e ) {
			throw new EncryptionException( e );
		}
	}

	/** Add Key Manager */
	public void addKeyManager( X509KeyManager keyManager ) {
		managers.add( keyManager );
	}

	/** Implementation of the aggregation */
	public String chooseClientAlias( String[] keyType, Principal[] issuers, Socket socket ) {
		for ( X509KeyManager keyManager : managers ) {
			String alias = keyManager.chooseClientAlias( keyType, issuers, socket );
			if ( alias != null ) {
				return alias;
			}
		}

		return null;
	}

	/** Implementation of the aggregation */
	public String chooseServerAlias( String keyType, Principal[] issuers, Socket socket ) {
		for ( X509KeyManager keyManager : managers ) {
			String alias = keyManager.chooseServerAlias( keyType, issuers, socket );
			if ( alias != null ) {
				return alias;
			}
		}
		return null;
	}

	/** Implementation of the aggregation */
	public X509Certificate[] getCertificateChain( String alias ) {
		for ( X509KeyManager keyManager : managers ) {
			X509Certificate[] chain = keyManager.getCertificateChain( alias );
			if ( chain != null && chain.length != 0 ) {
				return chain;
			}
		}
		return new X509Certificate[0];
	}

	/** Implementation of the aggregation */
	public String[] getClientAliases( String keyType, Principal[] issuers ) {
		List<String> clientAliases = new ArrayList<String>();

		for ( X509KeyManager keyManager : managers ) {
			for ( String alias : keyManager.getClientAliases( keyType, issuers ) ) {
				clientAliases.add( alias );
			}
		}

		return clientAliases.toArray( new String[clientAliases.size()] );
	}

	/** Implementation of the aggregation */
	public PrivateKey getPrivateKey( String alias ) {
		for ( X509KeyManager keyManager : managers ) {
			PrivateKey privateKey = keyManager.getPrivateKey( alias );
			if ( privateKey != null ) {
				return privateKey;
			}
		}
		return null;
	}

	/** Implementation of the aggregation */
	public String[] getServerAliases( String keyType, Principal[] issuers ) {
		for ( X509KeyManager keyManager : managers ) {
			String[] aliases = keyManager.getServerAliases( keyType, issuers );
			if ( aliases != null && aliases.length != 0 ) {
				return aliases;
			}
		}
		return new String[0];
	}
}